package net.penguinmenac3.mima.ui;

import java.awt.Dimension;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JSplitPane;
import java.awt.BorderLayout;

import javax.swing.JOptionPane;
import javax.swing.JList;
import javax.swing.AbstractListModel;
import javax.swing.JButton;
import javax.swing.JPanel;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Scanner;

import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.JSlider;
import javax.swing.JScrollPane;

public class MimaUI {
	private MIMA mima = null;
	private AddressMemory<String> memory = null;

	private String stored = null;
	private int line = 0;
	private int steps = 0;
	private String file = null;
	private boolean running = false;

	private JFrame frmMimaSimulator;
	private JTextArea code;
	private JSlider TickSleeps;
	private JList<String> variables;
	private JScrollPane scrollPane2;
	
	/**
	 * Launch the application.
	 */
	public static void main(final String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					MimaUI window = new MimaUI(args);
					window.frmMimaSimulator.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 * 
	 * @param initContent
	 */
	public MimaUI(String[] args) {
		String initContent = "";
		if (args.length > 0) {
			try {
				initContent = new Scanner(new File(args[0]))
						.useDelimiter("\\A").next();
				file = args[0];
			} catch (FileNotFoundException e) {
				initContent = "";
			}
		}
		stored = initContent;
		mima = new MIMA();
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frmMimaSimulator = new JFrame();
		frmMimaSimulator.setTitle("MIMA Simulator");
		frmMimaSimulator.setBounds(100, 100, 800, 600);
		frmMimaSimulator.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frmMimaSimulator.getContentPane().setLayout(new BorderLayout(0, 0));

		variables = new JList<String>();
		variables.setVisibleRowCount(0);
		variables.setAutoscrolls(false);
		scrollPane2 = new JScrollPane(variables);
		scrollPane2.setPreferredSize(new Dimension(200, 100));
		frmMimaSimulator.getContentPane().add(scrollPane2, BorderLayout.WEST);
		variables.setEnabled(false);
		variables.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		variables.setModel(new AbstractListModel<String>() {
			private static final long serialVersionUID = 1L;
			String[] values = new String[] { "Akku: 0x0", "Line: 0x0", " " };

			public int getSize() {
				return values.length;
			}

			public String getElementAt(int index) {
				return values[index];
			}
		});
		variables.setSelectedIndex(2);

		JPanel panel = new JPanel();
		frmMimaSimulator.getContentPane().add(panel, BorderLayout.SOUTH);

		JButton Step = new JButton("Step");
		Step.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				step();
			}
		});

		TickSleeps = new JSlider();
		TickSleeps.setToolTipText("Time to sleep per tick");
		TickSleeps.setMinorTickSpacing(40);
		TickSleeps.setMajorTickSpacing(200);
		TickSleeps.setValue(200);
		TickSleeps.setPaintLabels(true);
		TickSleeps.setPaintTicks(true);
		TickSleeps.setMaximum(1000);
		panel.add(TickSleeps);
		panel.add(Step);

		JButton Run = new JButton("Run");
		final MimaUI parent = this;
		Run.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				new Thread() {
					public void run() {
						parent.run();
					}
				}.start();
			}
		});
		panel.add(Run);

		JButton Reset = new JButton("Reset");
		Reset.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				reset();
			}
		});

		JButton Stop = new JButton("Stop");
		Stop.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				stop();
			}
		});
		panel.add(Stop);
		panel.add(Reset);

		JButton Diff = new JButton("Diff");
		Diff.setEnabled(false);
		Diff.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				diff();
			}
		});
		panel.add(Diff);

		JButton Save = new JButton("Save");
		Save.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				save();
			}
		});
		panel.add(Save);

		JButton Load = new JButton("Load");
		Load.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				load();
			}
		});
		panel.add(Load);

		code = new JTextArea();
		code.setText(stored);
		code.setTabSize(4);

		JScrollPane scrollPane = new JScrollPane(code);
		frmMimaSimulator.getContentPane().add(scrollPane, BorderLayout.CENTER);

	}

	/**
	 * Load a file.
	 */
	public void load() {
		String prevFile = file;
		try {
			file = JOptionPane.showInputDialog(
					"Insert a path and filename please", file);
			code.setText(new Scanner(new File(file)).useDelimiter("\\A").next());
		} catch (FileNotFoundException e) {
			JOptionPane.showConfirmDialog(code,
					"ERROR: Could not open the file.");
			file = prevFile;
		}
	}

	/**
	 * Save the current text to a file. Uses the currently opened file path, if
	 * it is set.
	 */
	public void save() {
		if (file == null) {
			file = JOptionPane
					.showInputDialog("Insert a path and filename please");
		}
		try {
			PrintWriter out = new PrintWriter(file);
			out.print(code.getText());
			out.close();
		} catch (FileNotFoundException e) {
			JOptionPane.showConfirmDialog(code,
					"ERROR: Could not write to the file.");
			file = null;
		}
	}

	/**
	 * Stop the mima simulator from stepping.
	 */
	public void stop() {
		running = false;
	}

	/**
	 * Automatically step the mima at the adjusted speed.
	 */
	public void run() {
		boolean done = false;
		running = true;
		while (!done && running) {
			done = !step();
			try {
				Thread.sleep(TickSleeps.getValue());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if (done) {
			line = 0;
			if (steps > 0) {
				System.out.println("Steps: " + steps);
				steps = 0;
			}
		}
	}

	/**
	 * Determine if there exists a next line for the mima.
	 * 
	 * @return True, iff there is another line.
	 */
	private boolean hasNextLine() {
		if (memory == null) {
			return true;
		}
		if (line < memory.getLastAddress() && line > -1) {
			return true;
		}
		return false;
	}

	/**
	 * Apply one step to the mima. The return type can be used to abort loops
	 * through the mima code.
	 * 
	 * @return if there was executed a step.
	 */
	public boolean step() {
		if (!hasNextLine()) {
			if (steps > 0) {
				System.out.println("Steps: " + steps);
				steps = 0;
			}
			line = 0;
			return false;
		}
		if (memory == null) {
			refresh();
		}
		String current = memory.load(line).replaceAll("\t", " ").replaceAll(" +",
				" ");
		String[] tokens = current.split(" ");
		int offset = 0;

		// ignore jump marks when parsing, they are only required for jumps
		if (tokens[0].endsWith(":")) {
			offset = 1;
		}

		String operation = tokens[offset];
		String parameter = null;
		if (tokens.length > offset + 1) {
			parameter = tokens[offset + 1];
		}
		int prevLine = line;
		line = mima.execute(operation, parameter, line, memory);

		String[] dump = memory.dump();
		String[] model = new String[dump.length + 4];
		model[0] = "AKKU: " + mima.getAkku();
		if (prevLine > -1) {
			model[1] = "LINE: " + String.format("%05x", prevLine);
		} else {
			model[1] = "LINE: none";
		}
		model[2] = "STEPS: " + steps;
		model[3] = " ";
		for (int i = 0; i < dump.length; i++) {
			model[i + 4] = dump[i];
		}

		variables.setListData(model);
		if (prevLine > -1)
			variables.setSelectedIndex(memory.findDumpPosition(prevLine) + 4);
		else
			variables.setSelectedIndex(2);

		variables.ensureIndexIsVisible(memory.findDumpPosition(prevLine) + 4);
		steps++;
		
		scrollPane2.repaint();
		return line != -1;
	}

	/**
	 * Refresh the memory from the edit pane. This resets changes in memory the
	 * mima has already done.
	 */
	public void refresh() {
		memory = new AddressMemory<String>();
		String tmp = code.getText().replaceAll("\r", "")
				.replaceAll("(\t| )+", " ").replaceAll("( *\n *)", "\n");
		String[] tmpMem = tmp.split("\n");
		stored = code.getText();
		int pointer = 0;
		for (int i = 0; i < tmpMem.length; i++) {
			while(tmpMem[i].startsWith(" ")) {
				tmpMem[i] = tmpMem[i].substring(1);
			}
			tmpMem[i] = tmpMem[i].replaceAll("\n", "").replaceAll(";.*", "");
			String[] split = tmpMem[i].split(" ");
			if (tmpMem[i].equals("")) {
				// ignore
				continue;
			} else if (tmpMem[i].startsWith("start")) {
				line = pointer;
				memory.label(pointer, "start");
				if (split.length > 2) {
					memory.store(pointer, split[1]+ " "+split[2]);
				} else {
					memory.store(pointer, split[1]);
				}
			} else if (tmpMem[i].startsWith("START")) {
				line = pointer;
				memory.label(pointer, "START");
				if (split.length > 2) {
					memory.store(pointer, split[1]+ " "+split[2]);
				} else {
					memory.store(pointer, split[1]);
				}
			} else if (split[0].endsWith(":")) {
				memory.label(pointer, split[0].substring(0, split[0].length()-1));
				if (split.length > 2) {
					memory.store(pointer, split[1]+ " "+split[2]);
				} else {
					memory.store(pointer, split[1]);
				}
			} else if (split[0].equals("*")) {
				pointer = MIMA.getInteger(split[2]);
				continue;
			} else {
				if (split.length > 1) {
					memory.store(pointer, split[0]+ " "+split[1]);
				} else {
					memory.store(pointer, split[0]);
				}
			}
			pointer++;
		}
	}

	/**
	 * This resets the complete mima.
	 */
	public void reset() {
		if (steps > 0) {
			System.out.println("Steps: " + steps);
			steps = 0;
		}
		line = 0;
		mima.resetMIMA();
		memory = null;
	}

	/**
	 * Diff the current memory and the editor pane to only change opcodes that
	 * changed and insert lines without resetting the memory.
	 */
	public void diff() {
		if (stored == null) {
			refresh();
			return;
		}
		String[] current = code.getText().split("\n");
		String[] prev = code.getText().split("\n");
		for (int i = 0; i < current.length; i++) {
			current[i] = current[i].replaceAll("\n", "");
			current[i] = current[i].replaceAll("\r", "");
		}
		for (int i = 0; i < prev.length; i++) {
			prev[i] = prev[i].replaceAll("\n", "");
			prev[i] = prev[i].replaceAll("\r", "");
		}
		// TODO diff algorithm. (sounds like work :( )
	}

}
